﻿using System.Text;

namespace Cosmos.Text.Internal
{
    /*
     * Reference to:
     *	https://github.com/KvanTTT/BaseNcoding/blob/master/BaseNcoding/Base32.cs
     *	Author: KvanTTT (Ivan Kochurkin)
     *	License:
     *		Apache License 2.0
     *		https://github.com/KvanTTT/BaseNcoding/blob/master/LICENSE
     * 
     */

    internal class Base32 : BaseXCore
    {
        public const string DefaultAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
        public const char DefaultSpecial = '=';

        public override bool HasSpecial => true;

        public Base32(string alphabet = DefaultAlphabet, char special = DefaultSpecial, Encoding textEncoding = null)
            : base(32, alphabet, special, textEncoding) { }

        public override string Encode(byte[] data)
        {
            var dataLength = data.Length;
            var result = new StringBuilder((dataLength + 4) / 5 * 8);

            byte x1, x2;
            int i;

            var length5 = (dataLength / 5) * 5;
            for (i = 0; i < length5; i += 5)
            {
                x1 = data[i];
                result.Append(Alphabet[x1 >> 3]);

                x2 = data[i + 1];
                result.Append(Alphabet[((x1 << 2) & 0x1C) | (x2 >> 6)]);
                result.Append(Alphabet[(x2 >> 1) & 0x1F]);

                x1 = data[i + 2];
                result.Append(Alphabet[((x2 << 4) & 0x10) | (x1 >> 4)]);

                x2 = data[i + 3];
                result.Append(Alphabet[((x1 << 1) & 0x1E) | (x2 >> 7)]);
                result.Append(Alphabet[(x2 >> 2) & 0x1F]);

                x1 = data[i + 4];
                result.Append(Alphabet[((x2 << 3) & 0x18) | (x1 >> 5)]);
                result.Append(Alphabet[x1 & 0x1F]);
            }

            switch (dataLength - length5)
            {
                case 1:
                    x1 = data[i];
                    result.Append(Alphabet[x1 >> 3]);
                    result.Append(Alphabet[(x1 << 2) & 0x1C]);

                    result.Append(Special, 6);
                    break;
                case 2:
                    x1 = data[i];
                    result.Append(Alphabet[x1 >> 3]);
                    x2 = data[i + 1];
                    result.Append(Alphabet[((x1 << 2) & 0x1C) | (x2 >> 6)]);
                    result.Append(Alphabet[(x2 >> 1) & 0x1F]);
                    result.Append(Alphabet[(x2 << 4) & 0x10]);

                    result.Append(Special, 4);
                    break;
                case 3:
                    x1 = data[i];
                    result.Append(Alphabet[x1 >> 3]);
                    x2 = data[i + 1];
                    result.Append(Alphabet[((x1 << 2) & 0x1C) | (x2 >> 6)]);
                    result.Append(Alphabet[(x2 >> 1) & 0x1F]);
                    x1 = data[i + 2];
                    result.Append(Alphabet[((x2 << 4) & 0x10) | (x1 >> 4)]);
                    result.Append(Alphabet[(x1 << 1) & 0x1E]);

                    result.Append(Special, 3);
                    break;
                case 4:
                    x1 = data[i];
                    result.Append(Alphabet[x1 >> 3]);
                    x2 = data[i + 1];
                    result.Append(Alphabet[((x1 << 2) & 0x1C) | (x2 >> 6)]);
                    result.Append(Alphabet[(x2 >> 1) & 0x1F]);
                    x1 = data[i + 2];
                    result.Append(Alphabet[((x2 << 4) & 0x10) | (x1 >> 4)]);
                    x2 = data[i + 3];
                    result.Append(Alphabet[((x1 << 1) & 0x1E) | (x2 >> 7)]);
                    result.Append(Alphabet[(x2 >> 2) & 0x1F]);
                    result.Append(Alphabet[(x2 << 3) & 0x18]);

                    result.Append(Special);
                    break;
            }

            return result.ToString();
        }

        public override byte[] Decode(string data)
        {
            unchecked
            {
                if (string.IsNullOrEmpty(data))
                    return new byte[0];

                int additionalBytes = 0, diff = 0, tempLen = 0;


                var lastSpecialInd = data.Length;
                while (data[lastSpecialInd - 1] == Special)
                    lastSpecialInd--;
                var tailLength = data.Length - lastSpecialInd;

                additionalBytes = tailLength switch
                {
                    6 => 4,
                    4 => 3,
                    3 => 2,
                    1 => 1,
                    _ => additionalBytes
                };

                diff = tailLength - additionalBytes;
                tailLength = additionalBytes;
                tempLen = data.Length - diff;

                var result = new byte[(tempLen + 7) / 8 * 5 - tailLength];
                var length5 = result.Length / 5 * 5;
                int x1, x2, x3, x4, x5, x6, x7, x8;

                int i, srcInd = 0;
                for (i = 0; i < length5; i += 5)
                {
                    x1 = InvAlphabet[data[srcInd++]];
                    x2 = InvAlphabet[data[srcInd++]];
                    x3 = InvAlphabet[data[srcInd++]];
                    x4 = InvAlphabet[data[srcInd++]];
                    x5 = InvAlphabet[data[srcInd++]];
                    x6 = InvAlphabet[data[srcInd++]];
                    x7 = InvAlphabet[data[srcInd++]];
                    x8 = InvAlphabet[data[srcInd++]];

                    result[i] = (byte) ((x1 << 3) | ((x2 >> 2) & 0x07));
                    result[i + 1] = (byte) ((x2 << 6) | ((x3 << 1) & 0x3E) | ((x4 >> 4) & 0x01));
                    result[i + 2] = (byte) ((x4 << 4) | ((x5 >> 1) & 0xF));
                    result[i + 3] = (byte) ((x5 << 7) | ((x6 << 2) & 0x7C) | ((x7 >> 3) & 0x03));
                    result[i + 4] = (byte) ((x7 << 5) | (x8 & 0x1F));
                }

                switch (tailLength)
                {
                    case 4:
                        x1 = InvAlphabet[data[srcInd++]];
                        x2 = InvAlphabet[data[srcInd++]];
                        result[i] = (byte) ((x1 << 3) | ((x2 >> 2) & 0x07));
                        break;
                    case 3:
                        x1 = InvAlphabet[data[srcInd++]];
                        x2 = InvAlphabet[data[srcInd++]];
                        x3 = InvAlphabet[data[srcInd++]];
                        x4 = InvAlphabet[data[srcInd++]];

                        result[i] = (byte) ((x1 << 3) | ((x2 >> 2) & 0x07));
                        result[i + 1] = (byte) ((x2 << 6) | ((x3 << 1) & 0x3E) | ((x4 >> 4) & 0x01));
                        break;
                    case 2:
                        x1 = InvAlphabet[data[srcInd++]];
                        x2 = InvAlphabet[data[srcInd++]];
                        x3 = InvAlphabet[data[srcInd++]];
                        x4 = InvAlphabet[data[srcInd++]];
                        x5 = InvAlphabet[data[srcInd++]];

                        result[i] = (byte) ((x1 << 3) | ((x2 >> 2) & 0x07));
                        result[i + 1] = (byte) ((x2 << 6) | ((x3 << 1) & 0x3E) | ((x4 >> 4) & 0x01));
                        result[i + 2] = (byte) ((x4 << 4) | ((x5 >> 1) & 0xF));
                        break;
                    case 1:
                        x1 = InvAlphabet[data[srcInd++]];
                        x2 = InvAlphabet[data[srcInd++]];
                        x3 = InvAlphabet[data[srcInd++]];
                        x4 = InvAlphabet[data[srcInd++]];
                        x5 = InvAlphabet[data[srcInd++]];
                        x6 = InvAlphabet[data[srcInd++]];
                        x7 = InvAlphabet[data[srcInd++]];

                        result[i] = (byte) ((x1 << 3) | ((x2 >> 2) & 0x07));
                        result[i + 1] = (byte) ((x2 << 6) | ((x3 << 1) & 0x3E) | ((x4 >> 4) & 0x01));
                        result[i + 2] = (byte) ((x4 << 4) | ((x5 >> 1) & 0xF));
                        result[i + 3] = (byte) ((x5 << 7) | ((x6 << 2) & 0x7C) | ((x7 >> 3) & 0x03));
                        break;
                }

                return result;
            }
        }
    }
}